<script setup lang='ts'>
import { SessionInfo, RoleEnum, SiderMenuEnum } from '@/models/@types';
import { appRef } from '@/models/app.ref';
import { FormatDate, clipboardWriteText, downloadTxtFile, formatTime } from '@/utils/common.methods';
import { Message } from '@arco-design/web-vue';
import { onMounted, onUnmounted, ref, watch } from 'vue';
import Prompt from './Prompt.vue';
import Explanation from '@/components/Explanation.vue'
import Guide from '@/components/chat/Guide.vue'
import { GeneratePromptReq, promptService } from '@/services/prompt.service';
import Markdown from '../Markdown.vue';
import router from '@/router';
import { commonService } from '@/services/common.service';
import { audioDB } from '@/db/audio';
import { PromptInfo } from '@/models/prompt';
import { appContext } from '@/models/app.context';

/**
 * 说明，公共会话组件 API 如下
 * -- props --
 * sessions     会话列表  SessionInfo[]
 * isSend       发送中
 * isrefresh    是否显示重新获取会话按钮
 * isGoodIdea   是否显示 Good Idea 按钮
 * chunk        流数据显示，仅在 isSend 为 true 时有效，isSend 结束后，需将该条消息写入  sessions 中
 * prompt       提示词 id
 *
 * -- emits --
 * send         发送   Parma： value:string   会话框中的文本内容
 * remove       删除   Parma： value:string   回答/问题 的 uid
 * refresh      重载   Parma： value:string   该回答的 quote
 *
 * -- slots --
 * select-model 选择模型插槽
 * button-bar    停止响应
 */
const props = withDefaults(defineProps<{ sessions: SessionInfo[], isSend?: boolean, isGoodIdea?: boolean, isrefresh?: boolean, isStopSend?: boolean, chunk?: string, isPrompt?: boolean; prompt?: PromptInfo, prologue?: string, hiddenBtnBar?: boolean }>(), {
  sessions: [] as any,
  isStopSend: true
});
const emits = defineEmits(['send', 'remove', 'refresh', 'stopSend', 'prompt', 'editTextarea']);
// slots = ['select-model']

watch(() => [props.isSend, props.sessions, props.chunk], () => {
  if (!props.isSend) {
    setRelated();
  }
  scrollTopBottom()
});


const chunkData = ref('');
watch(() => props.chunk, (newData) => {
  chunkData.value = newData;
});

onMounted(() => {
  scrollTopBottom();
  setRelated();

});

onUnmounted(() => {
  isAutoScroll.value = false;
});

const sessionBox = ref(null)
const isAutoScroll = ref(false);
const oldScollTop = ref(0);

function scrollTopBottom() {
  if (isAutoScroll.value) return;
  const { scrollHeight, clientHeight } = sessionBox.value;
  sessionBox.value.scrollTop = scrollHeight - clientHeight;
  oldScollTop.value = sessionBox.value.scrollTop;
}

const handleScroll = () => {
  const scrollTop = sessionBox.value.scrollTop;
  // 定义一个阈值，当滚动位置小于阈值时，执行操作
  if (scrollTop - oldScollTop.value < 0) {
    // 执行滚动向上的操作
    isAutoScroll.value = true;
  } else {
    // 执行滚动向下的操作
    isAutoScroll.value = false;
  }
  oldScollTop.value = scrollTop;
};

function onCopy(richtext: string): void {
  clipboardWriteText(richtext).then(() => {
    Message.success('复制成功')
  })
}

/** 一个好点子 */
function onGoodIdea(): void {
  // textarea.value = '这里应该是一个随机的好点子'
}

function onRelated(text: string): void {
  const value = text.replace(/^\d+\.\s*/, '');
  relateds.value = [];
  emits('send', value);
}

async function onRemove(uid: string): Promise<void> {
  await audioDB.remove(uid)
  emits('remove', uid)
}


const isRelateds = ref(true);
const relateds = ref([] as string[]);
let maxLen = 3;
function setRelated(): void {
  relateds.value = [];
  const list: GeneratePromptReq[] = props.sessions?.map(val => { return { role: val.role, content: val.richtext } })
  const delayed = (list: string[], i: number = 0) => {
    if (i < list?.length) {
      if (list[i]) relateds.value.push(list[i]);
      if (relateds.value?.length > maxLen) {
        relateds.value.shift()
      }
      if (isAutoScroll.value) {
        scrollTopBottom();
      };
      setTimeout(() => {
        delayed(list, ++i)
      }, Math.random() * 200);
    }
  }
  promptService.generate_prompt(list, (data: string) => {
    if (data && !data?.includes('请求被中止')) {
      const list = data.split('\n');
      delayed(list)
    } else {
      relateds.value = [];
    }
  })
}

function toMindMap(text: string): void {
  appRef.mindMap.add(SiderMenuEnum.Leaf);
  const curr = appRef.mindMap.currMenu;
  curr.originalWords = text;
  router.replace('/chat/mind-map');
}

/** 下载原数据 */
function downloadSource(): void {
  console.log(props.sessions);
  const content = props.sessions?.reduce((pre: string, val: SessionInfo) => {
    if (val.role === RoleEnum.System) {
      pre += `System: `
    } else if (val.role === RoleEnum.User) {
      pre += `User: `
    } else if (val.role === RoleEnum.AI) {
      pre += `AI: `
    }
    pre += val.richtext + '\n=====================================\n';
    return pre
  }, '');
  downloadTxtFile(content, 'zaiwen.top' + formatTime(new Date(), 'yyyy/MM/dd hh:mm:ss') + '.txt');
  Message.success("已保存至本地");
}

/** 当前播放的音频 */
const currTTS = ref('');
const ttsLoading = ref(false);
const audio = new Audio();
document.body.appendChild(audio);
audio.addEventListener("ended", () => {
  currTTS.value = '';
  ttsLoading.value = false;
  try { stopAudioFn(); } catch (error) { }
})
let stopAudioFn = null;

function onTTS(uid: string, txt: string): void {
  if (currTTS.value === uid) {
    audio.pause();
    stopAudioFn();
    currTTS.value = '';
    ttsLoading.value = false;
  } else {
    try { stopAudioFn(); } catch (error) { }
    // 创建一个新的MediaSource对象
    const mediaSource = new MediaSource();
    currTTS.value = uid;
    commonService.ttsV2(txt, audio, mediaSource, (fn) => {
      stopAudioFn = fn;
    })
  }
}

const isPromptShow = ref(false);
function onPrompt(id: string) {
  emits('prompt', id);
  isPromptShow.value = false;
}
</script>
<template>
  <div class="interactive flex-c">
    <div class="content flex-c" ref="sessionBox" @scroll="handleScroll">
      <div class="sessions">
        <Guide :abbreviations="!!sessions?.length || !!prologue">
          <template #content>
            <a-button style="margin-right: 6px;" @click="isPromptShow = true" v-if="isPrompt">
              <template #icon v-if="prompt?.role">
                <icon-close @click.stop="emits('prompt', '');" />
              </template>
              {{ prompt?.role || '选择提示词' }}
            </a-button>
            <slot name="select-model"> </slot>
          </template>
        </Guide>
        <div class="item flex-c" :style="{ 'align-items': 'flex-start' }" v-if="prologue">
          <div class="top flex">
            <img class="avatar" src="@/assets/images/ai_avatar.webp" alt="">
            <span class="date">{{ FormatDate(Date.now()) }} </span>
          </div>
          <Markdown class="richtext ai-richtext" :source="prologue" />
        </div>
        <div class="item flex-c" v-for="(item, i) in sessions" :key="item.uid"
          :style="{ 'align-items': item.role === RoleEnum.User ? 'flex-end' : item.role === RoleEnum.AI ? 'flex-start' : 'center' }">
          <div class="top flex" v-if="item.role === RoleEnum.User">
            <span v-if="!hiddenBtnBar">
              <a-tooltip content="编辑" mini>
                <icon-edit class="icon" @click="emits('editTextarea', item.richtext)" />
              </a-tooltip>
              <a-tooltip content="复制" mini>
                <icon-copy class="icon" @click="onCopy(item.richtext)" />
              </a-tooltip>
              <a-tooltip content="删除" mini>
                <icon-delete class="icon" @click="onRemove(item.uid)" />
              </a-tooltip>
            </span>
            <span class="date">{{ FormatDate(item.timestamp) }} </span>
            <img class="avatar" src="@/assets/icons/home/user.svg" alt="">
          </div>
          <div class="tip flex-c" v-else-if="item.role === RoleEnum.Tip" @click="onRemove(item.uid)">
            <Markdown class="" :source="item.richtext" />
          </div>
          <div class="top flex" v-else>
            <img class="avatar" src="@/assets/images/ai_avatar.webp" alt="">
            <span class="date">{{ FormatDate(item.timestamp) }} </span>
            <span v-if="!hiddenBtnBar">
              <a-tooltip content="删除" mini>
                <icon-delete class="icon" @click="onRemove(item.uid)" />
              </a-tooltip>
              <a-tooltip content="复制" mini>
                <icon-copy class="icon" @click="onCopy(item.richtext)" />
              </a-tooltip>
              <a-tooltip content="重新获取" mini>
                <icon-refresh class="icon" @click="emits('refresh', item.quote); chunkData = ''"
                  v-if="isrefresh && i > sessions?.length - 3" />
              </a-tooltip>
              <a-tooltip content="快速生成思维导图" mini>
                <icon-mind-mapping class="icon" @click="toMindMap(item.richtext)" />
              </a-tooltip>
              <a-tooltip content="语音播放" mini>
                <span v-if="currTTS === item.uid">
                  <a-spin class="icon" v-if="ttsLoading" />
                  <icon-pause-circle class="icon" v-else @click="onTTS(item.uid, item.richtext)" />
                </span>
                <icon-customer-service v-else class="icon" @click="onTTS(item.uid, item.richtext)" />
              </a-tooltip>
            </span>
          </div>
          <div class="richtext" v-if="item.role === RoleEnum.User" style="white-space: pre-line;"> {{ item.richtext }}
          </div>
          <!-- <Markdown class="richtext" v-if="item.role === RoleEnum.User" :source="item.richtext" :convert="true" /> -->
          <Markdown class="richtext ai-richtext" v-if="item.role === RoleEnum.AI" :source="item.richtext" />
          <div class="images" v-if="item.assets?.length">
            <template v-for="file in item.assets" :key="file.id">
              <a-image class="img" width="70" :src="file.snapshot" fit="cover" v-if="file.format?.includes('image')">
                <template #error>
                  <a-alert type="warning" style="width: max-content;position: absolute;right: 0;">图片过期</a-alert>
                </template>
              </a-image>
              <a-link :href="file.url" icon v-else>{{ file.name }}</a-link>
            </template>

          </div>
        </div>
        <div class="item flex-c" :style="{ 'align-items': 'flex-start' }" v-if="isSend">
          <div class="top flex">
            <img class="avatar" src="@/assets/images/ai_avatar.webp" alt="">
            <span class="date">{{ FormatDate(Date.now()) }} </span>
          </div>
          <Markdown class="richtext ai-richtext" :source="chunkData || '请稍候，数据请求中......'" />
        </div>
        <div class="item flex-c" :style="{ 'align-items': 'flex-start' }" v-if="!isSend && relateds?.length">
          <div class="top flex">
            <span class="avatar"></span>
            <span class="date"> 相关问题 </span>
            <icon-refresh class="icon" @click="setRelated" />
            <a-switch size="small" v-model="isRelateds" />
          </div>
          <p class="relateds" v-if="isRelateds" v-for="item in relateds" @click="onRelated(item)" :key="item">
            {{ item }}
          </p>
        </div>
      </div>
    </div>

    <div class="bottom">
      <div class="prompt flex">
        <div class="left flex">
          <a-button class="item" v-if="isGoodIdea" type="outline" @click="onGoodIdea" :loading="isSend">
            Good Idea
          </a-button>
          <a-button class="item" v-if="sessions?.length" @click="downloadSource">
            <template #icon> <icon-save /></template>
          </a-button>
          <a-button class="item" status="warning" type="primary" v-if="isSend && isStopSend" @click="emits('stopSend')">
            <template #icon> <icon-close /> </template>
            <span>停止响应</span>
          </a-button>
          <div class="item flex">
            <slot name="button-bar"></slot>
          </div>
        </div>
        <a-button class="to-bottom" shape="circle" @click="() => { isAutoScroll = false; scrollTopBottom() }">
          <template #icon> <icon-down /> </template>
        </a-button>
      </div>
    </div>
    <a-modal v-model:visible="isPromptShow" title="选择提示词" width="70vw"
      :fullscreen="appContext.system.data.viewWidth < 1000" :footer="false">
      <div style="overflow: auto">
        <Prompt @prompt="onPrompt($event)">
        </Prompt>
      </div>
    </a-modal>
  </div>
</template>
<style lang='scss' scoped>
.interactive {
  height: 100%;
  width: 100%;
  max-width: 1270px;
  overflow: hidden;

  &>* {
    width: 100%;
  }

  .content {
    flex: 1;
    overflow-y: auto;
    margin-top: 10px;
    padding: 10px;
    scroll-behavior: smooth;
    justify-content: space-between;
    position: relative;

    .sessions {
      width: 100%;
      flex: 1;

      .item {
        margin-bottom: 10px;
        width: 100%;

        .tip {
          border: 1px solid #d3d3d3;
          border-radius: 6px;
          overflow: hidden;
        }

        .top {
          .date {
            margin: 0 10px;
            font-size: 12px;
            color: var(--color-neutral-6);
          }

          .icon {
            cursor: pointer;
            margin: 0 5px;
            font-size: 16px;
          }

          .avatar {
            width: 30px;
            height: 30px;
            border-radius: 50%;
          }
        }

        .richtext {
          width: auto;
          max-width: calc(100% - 80px);
          margin: 5px 40px;
          border-radius: 3px;
          line-height: 23px;
          word-break: break-all;
          border-radius: 3px;
          box-shadow: 0 0 3px #00000042;
          background: rgb(var(--green-3));
          color: var(--color-bg-10);
          padding: 10px 20px;
          font-size: 16px;
        }

        .ai-richtext {
          background: var(--color-bg-1);
          padding: 0;

          ::v-deep p {
            margin: 0;
            padding: 0;
          }

          // ::v-deep * {
          //   color: #a9a9a9;
          // }
        }

        .images {
          margin: 5px 40px;

          .img {
            margin-left: 10px;
            min-height: 40px;
          }
        }

        .relateds {
          margin: 10px 40px 0;
          padding: 5px 10px;
          line-height: 20px;
          background: var(--color-neutral-3);
          color: var(--color-neutral-10);
          cursor: pointer;

          &:hover {
            background: var(--color-neutral-4);
          }
        }
      }


    }
  }



  .bottom {
    position: relative;
    align-items: flex-end;

    .prompt {
      width: 100%;
      margin: 10px 0 5px;
      justify-content: space-between;
      align-items: flex-start;
      position: relative;
      justify-content: center;


      @media screen and (min-width: 1200px) {
        align-items: flex-end;
        padding: 0;
      }

      .left {
        overflow-x: auto;
        max-width: 100%;

        .item {
          margin: 0;
          margin-right: 5px;

          &:last-child {
            margin: 0;
          }
        }
      }

      .right {
        max-width: 50%;
        overflow: hidden;
        align-items: flex-end;

        .item {
          width: fit-content;
          max-width: 100%;
          border-radius: 20px 20px 0 20px;
          height: auto;
          margin-bottom: 5px;
          cursor: pointer;
          white-space: pre-line;
          text-align: left;
          padding: 5px 10px;
          font-size: 13px;

          &:last-child {
            margin: 0;
          }
        }
      }

      .to-bottom {
        position: absolute;
        bottom: 40px;
        right: 14px;
      }

      @media screen and (max-width: 1200px) {

        .right {
          width: inherit;
          max-width: max-content;
          flex-direction: revert;
          overflow-x: auto;
          margin-top: 10px;

          .item {
            max-width: max-content;
            margin: 0;
            margin-right: 10px !important;
            border-radius: 2px;
            white-space: nowrap;

            &:last-child {
              margin: 0 !important;
            }
          }
        }
      }
    }

    .stopSendbtn {
      position: absolute;
      top: -15px;
      left: 50%;
      transform: translateX(-50%);
      z-index: 2;
    }

    .textarea {
      padding-right: 100px;
    }

    .send {
      position: absolute;
      right: 10px;
      bottom: 27px;
      // margin-left: 10px;
    }

    @media screen and (max-width: 1200px) {
      padding-bottom: 0;

      .send {
        position: absolute;
        right: 10px;
        bottom: 10px;
      }
    }
  }
}
</style>
